/*
 * scheduler.c
 *
 *  Created on: Mar 4, 2012
 *  the Scheduler has a simple API; it will:
 *	* Get the next runnable process
 *	* Block a process that's currently running and removes it from the runnable process rotation
 *	* Unblock a proccess that was blocked, and adds it to the runnable process rotation
 *
 *
 */
#include <stdlib.h>
#include <stdio.h>
#include "scheduler.h"
#include "process.h"
#include "../Utility/queue.h"
#include "../Processes/process_type.h"

/* Utility */
/*
 * enqueues the process in the correct priority queue
 */
int enqueue_priority(process * proc, scheduler * self){
	switch(proc->type){
	case 0:
		Enqueue(proc, self->priority_0_queue);
		break;
	case 1:
		Enqueue(proc, self->priority_1_queue);
		break;
	case 2:
		Enqueue(proc, self->priority_2_queue);
		break;
	case 3:
		Enqueue(proc, self->priority_3_queue);
		break;
	default:
		Enqueue(proc, self->priority_3_queue);
		break;
	}
}

/*
 * Switches Processes and prints output
 */
process * switch_process(process * to, process * from, scheduler * self){
	printf("Scheduler: Running process %d (%s) next.\n", to->pcb.process_id, to->name);

	from->pcb.state = RUNNABLE;
	to->pcb.state = RUNNING;

	if(self->operating_mode ==2){
		enqueue_priority(from, self);
	}
	else{
		Enqueue(from, self->my_process_queue);
	}
	self->my_running_process = to;
	return to;
}

void give_tickets(int number, process** pool, int index, process* holder){
	int i = index;
	// Horribly ineffecient list of Ticket pointers...
	// Only 1024 char inputs from command line  with 40 tickets worst case = 1024 * 40 sizeof(char) memory... not too scary...
	for(i = 0; i != number; i++){
		pool[i] = holder;
	}
}


/* scheduling algorithms */
process * get_next_round_robin_process_function(scheduler * self){
	process * proc;
	//Handle Current Running Process:
	//self->my_running_process->pcb.state = RUNNABLE;
	switch(self->my_running_process->pcb.state){
	case BLOCKED:
		//keep it blocked
		break;
	case RUNNING:
		self->my_running_process->pcb.state = RUNNABLE;
		break;
	case RUNNABLE:
		break;
	}

	Enqueue(self->my_running_process, self->my_process_queue);
	int index = 0;
	int q_size = QueueSize(self->my_process_queue);

	while(index < q_size){

		proc = (process *) FrontAndDequeue(self->my_process_queue);

		if(proc->pcb.state == BLOCKED){
			Enqueue(proc, self->my_process_queue);
		}else{
			return switch_process(proc, self->my_running_process,self);
		}
		index ++;
	}
	//we've iterated through the queue, and not a single good process, we will
	// now return NULL, the CPU go into 'wait for interrupts' mode
	return NULL;

}


process * get_next_priority_process_function(scheduler * self){

	int array_index = 0;
	int queue_index = 0;
	int priority_classes = sizeof(self->PRIORITY_ORDER) / sizeof(self->PRIORITY_ORDER[0]);
	printf("selecting between %d priority classes " , priority_classes);
	for (array_index = 0; array_index < priority_classes; array_index++){
		//get the top priority queue
		printf("looking into queue %d\n", queue_index);
		Queue current_queue = self->priority_queue_array[array_index];
		int queue_size = QueueSize(current_queue);

		for(queue_index = 0; queue_index < queue_size; queue_index++){

			process * proc = (process *) FrontAndDequeue(current_queue);

			if(proc->pcb.state == BLOCKED){
				Enqueue(proc, self->my_process_queue);
			}else{
				//switch out current process:
				switch_process(proc, self->my_running_process,self);
			}
		}
	}
}

process * get_next_lottery_process_function(scheduler * self){
	switch(self->my_running_process->pcb.state){
		case BLOCKED:
			//keep it blocked
			break;
		case RUNNING:
			self->my_running_process->pcb.state = RUNNABLE;
			break;
		case RUNNABLE:

			break;
		}
	// Lottery Algorithm:
	int i, winning_ticket = 0;
	Queue runnable_queue;
	for(i = 0; i < sizeof(self->my_process_array); i++){
		if((self->my_process_array[i])->pcb.state != BLOCKED){
			Enqueue(self->my_process_array[i], runnable_queue);
		}
	}
	// UNTESTED!!!  12:30pm... heading to bed...
	process** pool;
	int index = 0;
	while(!IsEmpty(runnable_queue)){
		process* proc = (process*)FrontAndDequeue(runnable_queue);
		int num_tickets = 0;
		switch(proc->type){
			case NUMBER_CRUNCHER:
				num_tickets = 10;
				break;
			case CONSUMER:
				num_tickets = 20;
				break;
			case PRODUCER:
				num_tickets = 30;
				break;
			case USER_INTERFACE:
				num_tickets = 40;
				break;
		}
		give_tickets(num_tickets, pool, index, proc);
		index += num_tickets;
	}
	if (index == 0){return NULL;} // No Unblocked Processes.
	winning_ticket = rand() % index;
	return pool[winning_ticket];
}

/* utilitiy functions */
process * findIndex(scheduler * self, int id){
	int i = 0;
	while(i < self->total_process_count){
		process * proc = self->my_process_array[i];
		if(proc->pcb.process_id == id){
			return proc;
		}
		i++;
	}
	return NULL; //not found
}

/* public api */
int unblock_process(scheduler * self , int process_id){
	//convert process id to index
	process * proc = findIndex(self, process_id);
	if(!proc == NULL){
		proc->pcb.state = RUNNABLE;
	}
	else{
		//couldn't find index
		return -1;
	}
	return 0;
}

int block_running_process(scheduler * self, int process_id){
	//convert process id to index
	process * proc = findIndex(self, process_id);
	printf("Blocking process %s\n", proc->name);
	if(proc->pcb.waiting_on != -1) { //-1 sentinal value is best sentinal value
		//we've got to add this to the memory blocked queue
		Enqueue(proc, self->memory_blocked_processes);
	}
	proc->pcb.state = BLOCKED;
	if(!proc == NULL){
		proc->pcb.state = BLOCKED;
	}
	else{
		//couldn't find index
		return -1;
	}
	return 0;
}

int unblock_memory(scheduler * self, int memory_location){
	int blocked_items = QueueSize(self->memory_blocked_processes);
	printf("Blocked items: %d \n", blocked_items);
	int i = 0;
	//for each item blocked by memory:
	for(i = 0; i < blocked_items; i++){
		process * proc = FrontAndDequeue(self->memory_blocked_processes);
		if(proc->pcb.waiting_on == memory_location){
			//the process is waiting on that specific address, unblock it
			proc->pcb.state = RUNNABLE;
			printf("a process has been unblocked ! \n");
		}
		else{
			//not this time, jhonny
			printf("Memory locationd %d did not unblock process %d \nwaiting on memory location %d ! \n" ,memory_location, proc->pcb.process_id, proc->pcb.waiting_on);
			Enqueue(proc, self->memory_blocked_processes);
		}
	}
}
int block_mem_process(scheduler * self,int process_id){
	//convert process id to index
	process * proc = findIndex(self, process_id);
	printf("Memory is blocking process %s\n", proc->name);
	proc->pcb.state = BLOCKED;
	if(!proc == NULL){
		proc->pcb.state = BLOCKED;
	}
	else{
		//couldn't find index
		return -1;
	}
	return 0;
}

/*end public api */


/* Tells us what's going on inside the scheduler */
/* in the format:
 *
 *
  Process 3 (Producer) incremented memory location 0. // CPU
  Scheduler: Running process 3 (Producer) next.
  Process 0 (Calculator)'s state: RUNNABLE
  Process 1 (UI)'s state: BLOCKED
  Process 2 (Consumer)'s state: RUNNABLE
  Process 3 (Producer)'s state: RUNNING

 */
void  printOutput(scheduler * self){
	//output = "testing%s ", "another"
	char* names[] = {"RUNNING","RUNNABLE","BLOCKED"};
	int i = 0;
	int total= self->total_process_count;
	process ** processes = self->my_process_array;
	for(i = 0; i < total; i++){
		process * proc = processes[i];
		printf("\tProcess %d (%s)'s state: %s:.\n", proc->pcb.process_id, proc->name, names[proc->pcb.state]);
	}
	printf("\n");
}

scheduler * scheduler_init(int operating_mode, process ** processes, int num_processes){

	/* scheduler */
	scheduler * self = malloc(sizeof(scheduler));
	/* process */
	self->my_process_array = malloc( sizeof(process) * num_processes );
	//setup all processes as ready:
	self->total_process_count = num_processes;

	//self->priority_queue_array =  malloc(sizeof(Queue) * 4);


	self->my_process_queue = CreateQueue(5 + num_processes);
	self->memory_blocked_processes = CreateQueue(5 + num_processes);

	/* setup priority Queues */
	self->priority_0_queue = CreateQueue(5 + num_processes);
	self->priority_1_queue = CreateQueue(5 + num_processes);
	self->priority_2_queue = CreateQueue(5 + num_processes);
	self->priority_3_queue = CreateQueue(5 + num_processes);

	//
	self->priority_queue_array[0] = self->priority_0_queue;
	self->priority_queue_array[1] = self->priority_1_queue;
	self->priority_queue_array[2] = self->priority_2_queue;
	self->priority_queue_array[3] = self->priority_3_queue;


	//define priority order
	self->PRIORITY_ORDER[0] = 3; //3 is highest priority
	self->PRIORITY_ORDER[1] = 2;
	self->PRIORITY_ORDER[2] = 1;
	self->PRIORITY_ORDER[3] = 0;


	int temp_index;
	//printf("there are %d processes ready to be copied into scheduler\n", num_processes);
	for(temp_index = 0; temp_index < num_processes; temp_index++){
		process * proc =  processes[temp_index];
		//printf("proc id being copied to scheduler's ready processes %d\n", proc->pcb.process_id);
		/* copy process based on proccess type to appropriate queue */
		switch(proc->type){
		case 0:
			Enqueue(proc, self->priority_0_queue);
			break;
		case 1:
			Enqueue(proc, self->priority_1_queue);
			break;
		case 2:
			Enqueue(proc, self->priority_2_queue);
			break;
		case 3:
			Enqueue(proc, self->priority_3_queue);
			break;
		default:
			break;
		}
		self->my_process_array[temp_index] = proc;
		Enqueue(proc, self->my_process_queue);
	}

	self->operating_mode = operating_mode;
	//0: round-robin
	//1: for priority
	//2: Lottery
	char * name;
	switch(operating_mode){
		case 0:
			name = "Round Robin";
			self->get_next_process = &get_next_round_robin_process_function;
			break;
		case 1:
			name = "Priority";
			self->get_next_process = &get_next_priority_process_function;
			break;
		case 2:
			name = "Lottery";
			self->get_next_process = &get_next_lottery_process_function;
			break;
		default:
			name = "Unrecognided Command - Defaulting to Round Robin";
			self->get_next_process = &get_next_priority_process_function;
			break;
	}
	printf("User selected %s Scheduling \n", name);
	//set up initial running process
	self->my_running_process = (process *) FrontAndDequeue(self->my_process_queue);
	//self->my_running_process = get_next_round_robin_process_function(self);
	return self;
}
